﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.IO;
using System.Diagnostics;
using System.Web;
using System.Collections.Specialized;

namespace Snom
{
    class EventsHttpListener
    {
        public event SnomGenericEventHandler EventArrived;
        public event ErrorEventHandler Error;

        private TcpListener _listener;

        public EventsHttpListener(int listeningPort)
        {
            HttpServerPort = listeningPort;
        }

        public int HttpServerPort
        {
            get;
            set;
        }

        public bool IsActive
        {
            get;
            set;
        }

        public void Start()
        {
            Thread thread = new Thread(new ThreadStart(Listen));
            thread.Start();
        }

        private void Listen()
        {
            IPAddress localIpAddress = NetUtil.GetLocalIpAddress();

            _listener = new TcpListener(localIpAddress, HttpServerPort);
            _listener.Start();

            IsActive = true;

            while (IsActive)
            {
                TcpClient tcpClient = _listener.AcceptTcpClient();
                ThreadPool.QueueUserWorkItem(new WaitCallback(ProcessClientRequest), tcpClient);
            }
        }

        private void ProcessClientRequest(object state)
        {
            try
            {
                TcpClient tcpClient = state as TcpClient;

                BufferedStream requestStream = new BufferedStream(tcpClient.GetStream());

                string url = GetRequestUrl(requestStream);

                StreamWriter responseStream = new StreamWriter(new BufferedStream(tcpClient.GetStream()));

                WriteSuccessResponse(responseStream);

                responseStream.Flush();

                requestStream = null;
                responseStream = null;
                tcpClient.Close();

                SnomGenericEventArgs args = EventArgsFromUrl(url);

                RaiseEventArrived(args);
            }
            catch (Exception e)
            {
                RaiseError(new Exception("Error processing snom event.", e));
            }
        }

        public SnomGenericEventArgs EventArgsFromUrl(string url)
        {
            if (url.StartsWith("/Events?"))
            {
                url = url.Remove(0, 8);
            }
            // TODO: Replace this, because it requires .NET 4 (instead of .NET 4 Client Profile)
            NameValueCollection vars = HttpUtility.ParseQueryString(url);

            SnomGenericEventArgs args = new SnomGenericEventArgs();

            foreach (string varName in vars.Keys)
            {
                string varValue = vars[varName];
                
                switch (varName)
                {
                    case "event_type":
                        args.EventType = (SnomEventType) Enum.Parse(typeof(SnomEventType), varValue);
                        break;
                    case "local":
                        args.CalleeSipUri = varValue;
                        break;
                    
                    case "remote":
                        args.CallerSipUri = varValue;
                        break;
                    
                    case "active_url":
                        args.ActiveOutgoingIdentitySipUri = varValue;
                        break;
                    
                    case "active_user":
                        args.ActiveOutgoingIdentityUser = varValue;
                        break;
                    
                    case "active_host":
                        args.ActiveOutgoingIdentityHost = varValue;
                        break;
                    
                    case "csta_id":
                        args.CstaId = varValue;
                        break;
                    
                    case "call-id":
                        args.ActiveCallId = varValue;
                        break;
                    
                    case "display_local":
                        args.CalleeDisplayName = varValue;
                        break;
                    
                    case "display_remote":
                        args.CallerDisplayName = varValue;
                        break;

                    case "expansion_module":
                        args.ExpansionModule = varValue;
                        break;

                    case "active_key":
                        args.ActiveCallFunctionKey = varValue;
                        break;
                }
            }

            return args;
        }

        private string GetRequestUrl(BufferedStream requestStream)
        {
            string firstLine = StreamReadLine(requestStream);

            string[] parts = firstLine.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);

            if (parts.Length != 3)
            {
                throw new Exception("Invalid http request: " + firstLine);
            }

            string url = parts[1];

            return url;
        }

        public void WriteSuccessResponse(StreamWriter writer)
        {
            writer.WriteLine("HTTP/1.0 200 OK");
            writer.WriteLine("Content-Type: text/html");
            writer.WriteLine("Connection: close");
            writer.WriteLine("");
        }

        private string StreamReadLine(Stream inputStream)
        {
            int next_char;
            string data = "";
            while (true)
            {
                next_char = inputStream.ReadByte();
                if (next_char == '\n') { break; }
                if (next_char == '\r') { continue; }
                if (next_char == -1) { Thread.Sleep(1); continue; };
                data += Convert.ToChar(next_char);
            }
            return data;
        }

        private void RaiseEventArrived(SnomGenericEventArgs args)
        {
            if (EventArrived != null)
            {
                EventArrived(this, args);
            }
        }

        private void RaiseError(Exception error)
        {
            if (Error != null)
            {
                Error(this, new ErrorEventArgs(error));
            }
        }
    }
}
